<?php
// @formatter:off
/**
 * @file WCPageCache.php
 * @author Alejandro Dario Simi
 * @date $Date: 2013-04-25 00:13:34 +0000 (Thu, 25 Apr 2013) $
 *
 * $Id: WCPageCache.php 61 2013-04-25 00:13:34Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/tags/wcomix-1.0-BETA1/includes/WCPageCache.php $
 */
// @formatter:on

define("WC_CACHE_COMPRESS_LEVEL", 2);

/**
 * @class WCPageCache
 */
class WCPageCache {
	/**
	 * @var WCPageCache
	 */
	protected static $_Instance;

	protected $_strings = array();
	protected $_db;
	protected $_dbprefix;
	protected $_code;
	protected $_enabled;
	protected $_anyGroup = "N";
	protected $_anyCollection = "N";
	protected $_anyTag = "N";
	protected $_anyDirectory = "N";
	protected $_anyComic = "N";
	protected $_anyRule = "N";
	protected $_onShell = false;
	protected $_lastInfo = array(
		"id" => false,
		"code" => false,
		"compressed" => false,
		"uncompressed" => false,
		"rate" => false
	);

	protected function __construct() {
		global $wcDatabaseAccess;
		global $wcDefaults;
		global $wcUser;

		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $wcDatabaseAccess['prefix'];

		if(empty($_SERVER['SHELL'])) {
			$this->_code = "{$_SERVER["SCRIPT_NAME"]}?{$_SERVER["QUERY_STRING"]}";
		} else {
			$this->_code = $_SERVER["SCRIPT_NAME"];
			$this->_onShell = true;
		}
		if($wcUser) {
			$this->_code = "{$wcUser->id()}|{$this->_code}";
		}
		$this->_code = base64_encode($this->_code);

		$this->_enabled = $wcDefaults['page-cache'] && !isset($_REQUEST['NOPAGECAGE']);
	}
	public function __destruct() {
	}
	/**
	 * Prevent users to clone the instance.
	 */
	public function __clone() {
		trigger_error(__CLASS__.": Clone is not allowed.", E_USER_ERROR);
	}
	//
	// Public methods
	public function append($id, $string) {
		if(isset($this->_strings[$id])) {
			$this->_strings[$id] .= $string;
		} else {
			$this->_strings[$id] = $string;
		}
	}
	public function anyCollection($status = true) {
		$this->_anyCollection = $status ? "Y" : "N";
	}
	public function anyComic($status = true) {
		$this->_anyComic = $status ? "Y" : "N";
	}
	public function anyDirectory($status = true) {
		$this->_anyDirectory = $status ? "Y" : "N";
	}
	public function anyGroup($status = true) {
		$this->_anyGroup = $status ? "Y" : "N";
	}
	public function anyRule($status = true) {
		$this->_anyRule = $status ? "Y" : "N";
	}
	public function anyStatus() {
		return array(
			"group" => $this->_anyGroup == "Y",
			"collection" => $this->_anyCollection == "Y",
			"tag" => $this->_anyTag == "Y",
			"directory" => $this->_anyDirectory == "Y",
			"comic" => $this->_anyComic == "Y",
			"rule" => $this->_anyRule == "Y"
		);
	}
	public function anyTag($status = true) {
		$this->_anyTag = $status ? "Y" : "N";
	}
	public function code() {
		return $this->_code;
	}
	public function disabled() {
		$this->_enabled = false;
		return $this->enabled();
	}
	public function enabled() {
		return $this->_enabled;
	}
	public function get($id, $stats = true, $code = false) {
		$auxCode = $code === false ? $this->code() : $code;

		if(($this->enabled() || $code) && !isset($this->_strings[$id])) {
			$this->clear($id, $code);

			foreach($this->_lastInfo as $key => $value) {
				$this->_lastInfo[$key] = false;
			}

			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  cch_data,\n";
				$query .= "        cch_length,\n";
				$query .= "        cch_full_length\n";
				$query .= "from    {$this->_dbprefix}cache\n";
				$query .= "where   cch_id   = :id\n";
				$query .= " and    cch_code = :code";
				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(
				":id" => $id,
				":code" => $auxCode
			);
			if($stmt->execute($params) && $stmt->rowCount() > 0) {
				$row = $stmt->fetch();
				$this->_strings[$id] = gzuncompress(base64_decode($row['cch_data']));

				$this->_lastInfo["id"] = $id;
				$this->_lastInfo["code"] = $auxCode;
				$this->_lastInfo["compressed"] = $row['cch_length'];
				$this->_lastInfo["uncompressed"] = $row['cch_full_length'];
				$this->_lastInfo["rate"] = round($row['cch_length'] / ($row['cch_full_length'] > 0 ? $row['cch_full_length'] : 1) * 100);

				if($stats) {
					$this->_strings[$id] .= "<!--\n";
					$this->_strings[$id] .= "CACHE\n";
					$this->_strings[$id] .= "ID:   {$this->_lastInfo["id"]}\n";
					$this->_strings[$id] .= "CODE: {$this->_lastInfo["code"]}\n\n";
					$this->_strings[$id] .= "COMPRESSED:   {$this->_lastInfo["compressed"]}\n";
					$this->_strings[$id] .= "UNCOMPRESSED: {$this->_lastInfo["uncompressed"]}\n";
					$this->_strings[$id] .= "RATE:         {$this->_lastInfo["rate"]}%\n";
					$this->_strings[$id] .= "-->\n";
				}
			}
		}

		return isset($this->_strings[$id]) ? $this->_strings[$id] : false;
	}
	public function invalidateByComic(WCComic &$comic) {
		if($comic) {
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}cache\n";
				$query .= "set     cch_invalid = 'Y'\n";
				$query .= "where   cch_comic     = :id";
				$query .= " or     cch_any_comic = 'Y'";
				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(":id" => $comic->id());
			$stmt->execute($params);

			//
			// Invalidating tags where the comic appears.
			global $wcTagsHolder;
			foreach($wcTagsHolder->tagsOf($comic) as $tag) {
				$this->invalidateByTag($tag);
			}

			//
			// Invalidating the directory where the comic is stored.
			$auxdir = $comic->directory();
			$this->invalidateByDirectory($auxdir);
		}
	}
	public function invalidateByDirectory(WCDirectory &$directory) {
		if($directory) {
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}cache\n";
				$query .= "set     cch_invalid = 'Y'\n";
				$query .= "where   cch_directory     = :id";
				$query .= " or     cch_any_directory = 'Y'";
				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(":id" => $directory->id());
			$stmt->execute($params);
		}
	}
	public function invalidateByRule(WCRule &$rule) {
		if($rule) {
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}cache\n";
				$query .= "set     cch_invalid = 'Y'\n";
				$query .= "where   cch_rule     = :id";
				$query .= " or     cch_any_rule = 'Y'";
				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(":id" => $rule->id);
			$stmt->execute($params);
		}
	}
	public function invalidateByTag(WCTag &$tag) {
		if($tag) {
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}cache\n";
				$query .= "set     cch_invalid = 'Y'\n";
				$query .= "where   cch_tag     = :id";
				$query .= " or     cch_any_tag = 'Y'";
				$stmt = $this->_db->prepare($stmtId, $query);
			}

			$params = array(":id" => $tag->id);
			$stmt->execute($params);
		}
	}
	public function lastInfo() {
		return $this->_lastInfo;
	}
	public function reset($id, $code = false) {
		$auxCode = $code === false ? $this->code() : $code;

		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from     {$this->_dbprefix}cache\n";
			$query .= "where           cch_id   = :id\n";
			$query .= " and            cch_code = :code\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
			":id" => $id,
			":code" => $auxCode
		);
		$stmt->execute($params);

		unset($this->_strings[$id]);
	}
	public function resetFullId($id) {
		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from     {$this->_dbprefix}cache\n";
			$query .= "where           cch_id   = :id\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(":id" => $id);
		$stmt->execute($params);

		unset($this->_strings[$id]);
	}
	public function save($id, $code = false) {
		$auxCode = $code === false ? $this->code() : $code;

		if(($this->enabled() || $code) && isset($this->_strings[$id])) {
			$this->forcedClear($id, $code);

			$data = base64_encode(gzcompress($this->_strings[$id], WC_CACHE_COMPRESS_LEVEL));
			$gzlen = strlen($data);
			$len = strlen($this->_strings[$id]);

			if($gzlen < 65535) {
				global $wcGroup;
				global $wcCollection;
				global $wcTag;
				global $wcDirectory;
				global $wcComic;
				global $wcRule;

				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}cache (cch_id, cch_code, cch_data, cch_length, cch_full_length,\n";
					$query .= "                cch_group, cch_collection, cch_tag, cch_directory, cch_comic, cch_rule,\n";
					$query .= "                cch_any_group, cch_any_collection, cch_any_tag, cch_any_directory, cch_any_comic, cch_any_rule)\n";
					$query .= "        values (:id, :code, :data, :gzlen, :len, :group, :collection, :tag, :directory, :comic, :rule,\n";
					$query .= "                :any_group, :any_collection, :any_tag, :any_directory, :any_comic, :any_rule)\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}

				$params = array(
					":id" => $id,
					":code" => $auxCode,
					":data" => $data,
					":gzlen" => $gzlen,
					":len" => $len,

					":group" => $wcGroup ? $wcGroup->id : null,
					":collection" => $wcCollection ? $wcCollection->id : null,
					":tag" => $wcTag ? $wcTag->id : null,
					":directory" => $wcDirectory ? $wcDirectory->id : null,
					":comic" => $wcComic ? $wcComic->id : null,
					":rule" => $wcRule ? $wcRule->id : null,

					":any_group" => $this->_anyGroup,
					":any_collection" => $this->_anyCollection,
					":any_tag" => $this->_anyTag,
					":any_directory" => $this->_anyDirectory,
					":any_comic" => $this->_anyComic,
					":any_rule" => $this->_anyRule
				);
				$stmt->execute($params);
			} else {
				echo "<!--\ncache larger than 65535 bytes\n-->\n";
			}
		}
	}
	//
	// Protected methods
	protected function clear($id, $code = false) {
		$auxCode = $code === false ? $this->code() : $code;
		{
		}

		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from {$this->_dbprefix}cache\n";
			$query .= "where       cch_id   = :id\n";
			$query .= " and        cch_code = :code\n";
			$query .= " and        (cch_invalid = 'Y' or (now() - cch_date) > 3600)\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
			":id" => $id,
			":code" => $auxCode
		);
		$stmt->execute($params);

		if($stmt->rowCount() > 0 && isset($this->_strings[$id])) {
			unset($this->_strings[$id]);
		}
	}
	protected function forcedClear($id, $code = false) {
		$auxCode = $code === false ? $this->code() : $code;

		$stmt = null;
		$stmtId = __CLASS__."::".__FUNCTION__;
		if($this->_db->has($stmtId)) {
			$stmt = $this->_db->get($stmtId);
		} else {
			$query = "delete from {$this->_dbprefix}cache\n";
			$query .= "where       cch_id   = :id\n";
			$query .= " and        cch_code = :code\n";
			$stmt = $this->_db->prepare($stmtId, $query);
		}

		$params = array(
			":id" => $id,
			":code" => $auxCode
		);
		$stmt->execute($params);
	}
	//
	// Public class methods
	public static function Instance() {
		if(!isset(self::$_Instance)) {
			$c = __CLASS__;
			self::$_Instance = new $c();
		}

		return self::$_Instance;
	}
}
?>
